package thinkingJava.chapter15;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

interface Combiner<T> {
	T combine(T x, T y);
}
interface UnaryFunction<R,T> {
	R function(T x);
}
interface Collector<T> extends UnaryFunction<T,T> {
	T result();
}
interface UnaryPredicate<T> {
	boolean test(T x);
}
public class Functional {
	public static <T> T reduce(Iterable<T> seq, Combiner<T> combiner) {
		Iterator<T> it = seq.iterator();
		if (it.hasNext()) {
			T result = it.next();
			while (it.hasNext()) {
				result = combiner.combine(result, it.next());
			}
			return result;
		}
		return null;
	}
	public static <T> Collector<T> forEach(Iterable<T> seq, Collector<T> func) {
		for (T t : seq) {
			func.function(t);
		}
		return func;
	}
	public static <R,T> List<R> transform(Iterable<T> seq, UnaryFunction<R,T> func) {
		List<R> result = new ArrayList<R>();
		for (T t : seq) {
			result.add(func.function(t));
		}
		return result;
	}
	public static <T> List<T> filter(Iterable<T> seq, UnaryPredicate<T> pred) {
		List<T> result = new ArrayList<T>();
		for (T t : seq) {
			if (pred.test(t)) {
				result.add(t);
			}
		}
		return result;
	}
	static class IntegerAdder implements Combiner<Integer> {

		public Integer combine(Integer x, Integer y) {
			return x + y;
		}

	}
	static class IntegerSubtracter implements Combiner<Integer> {

		public Integer combine(Integer x, Integer y) {
			return x - y;
		}

	}
	static class BigDecimalAdapter implements Combiner<BigDecimal> {

		public BigDecimal combine(BigDecimal x, BigDecimal y) {
			return x.add(y);
		}

	}
	static class BigIntegerAdapter implements Combiner<BigInteger> {

		public BigInteger combine(BigInteger x, BigInteger y) {
			return x.add(y);
		}

	}
	static class AtomicLongAdapter implements Combiner<AtomicLong> {

		public AtomicLong combine(AtomicLong x, AtomicLong y) {
			return new AtomicLong(x.addAndGet(y.get()));
		}

	}
	static class BigDecimalUlp implements UnaryFunction<BigDecimal, BigDecimal> {

		public BigDecimal function(BigDecimal x) {
			return x.ulp();
		}

	}
	static class GreaterThan<T extends Comparable<T>> implements UnaryPredicate<T> {
		private T bound;
		public GreaterThan(T bound) {
			this.bound = bound;
		}
		public boolean test(T x) {
			return x.compareTo(bound) > 0;
		}

	}
	static class MultiplyingIntegerCollector implements Collector<Integer> {
		private Integer val = 1;
		public Integer function(Integer x) {
			val *= x;
			return val;
		}
		public Integer result() {
			return val;
		}

	}
	public static void main(String[] args) {
		List<Integer> li = Arrays.asList(1,2,3,4,5,6,7);
		Integer result = reduce(li, new IntegerAdder());
		System.out.println(result);
		result = reduce(li, new IntegerSubtracter());
		System.out.println(result);
		print(filter(li, new GreaterThan<Integer>(4)));
		print(forEach(li, new MultiplyingIntegerCollector()).result());
		print(forEach(filter(li, new GreaterThan<Integer>(4)), new MultiplyingIntegerCollector()).result());
		MathContext mc = new MathContext(7);
		List<BigDecimal> lbd = Arrays.asList(
				new BigDecimal(1.1, mc), new BigDecimal(2.2, mc),
				new BigDecimal(3.3, mc), new BigDecimal(4.4, mc) );
		BigDecimal rbd = reduce(lbd, new BigDecimalAdapter());
		print(rbd);
		print(filter(lbd, new GreaterThan<BigDecimal>(new BigDecimal(3))));
		List<BigInteger> lbi = new ArrayList<BigInteger>();
		BigInteger bi = BigInteger.valueOf(11);
		for (int i = 0; i < 11; i++) {
			lbi.add(bi);
			bi = bi.nextProbablePrime();
		}
		print(lbi);
		BigInteger rbi = reduce(lbi, new BigIntegerAdapter());
		print(rbi);
		print(rbi.isProbablePrime(5));
		List<AtomicLong> lal = Arrays.asList(
				new AtomicLong(11), new AtomicLong(47),
				new AtomicLong(74), new AtomicLong(133));
		AtomicLong ral = reduce(lal, new AtomicLongAdapter());
		print(ral);
		print(transform(lbd, new BigDecimalUlp()));
	}
	public static void print(Object o) {
		System.out.println(o);
	}
}
